package com.example.emos.wx.config.shiro;

import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 因为在OAuth2Filter类中要读写ThreadLocal中的数据，
 * 所以OAuth2Filter类必须要设置成多例的，否则ThreadLocal将无法使用。
 * @author liujingmao
 * @version 1.0
 * @date 2021/1/31 22:25
 */
@Component
@Scope("prototype")
public class OAuth2Filter extends AuthenticatingFilter {

    @Autowired
    private ThreadLocalToken threadLocalToken;

    @Value("${emos.jwt.cache-expire}")
    private int cacheExpire;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private
    RedisTemplate redisTemplate;

    /**
     * 拦截请求之后，用于把令牌字符串封装成令牌对象
     * @param request
     * @param servletResponse
     * @return
     * @throws Exception
     */

    @Override
    protected AuthenticationToken createToken(ServletRequest request,
                                    ServletResponse servletResponse) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);

        if(StringUtils.isBlank(token)){
            return null;
        }
        return new OAuth2Token(token);
    }



    @Override
    protected
    boolean isAccessAllowed(ServletRequest request,
                            ServletResponse response,
                            Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        //Ajax 提交 application/json数据的时候，会先发出Option请求
        //这里要放行Option请求，不需要Shrio处理
        if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
            return true;
        }
        return false;
    }

    /**
     * 该方法用于处理所有应该被shiro处理请求
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected
    boolean onAccessDenied(ServletRequest request,
                           ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        resp.setHeader("Content-Type","text/html;charset=UTF-8");
        //允许跨域请求
        resp.setHeader("Access-Control-Allow-Credentials","true");
        resp.setHeader("Access-Control-Allow-Origin",req.getHeader("Origin"));

        threadLocalToken.clear();

        //获取请求token,如果token不存在，直接返回401
        String token = getRequestToken((HttpServletRequest) request );
        if(StringUtils.isBlank(token)){
            resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
            resp.getWriter().print("无效的令牌");
            return false;
        }

        try {
            //检查令牌是否过期
            jwtUtil.verifierToken(token);
        } catch (TokenExpiredException e){
            //如果客户端令牌过期，则查询redis中是否存在token,
            //如果存在token,则重新生成一个token给客户端
            if(redisTemplate.hasKey(token)){
                //删除token
               redisTemplate.delete(token);
               int userId = jwtUtil.getUserId(token);
               // 生成新的token
               token = jwtUtil.createToken(userId);
               // 把新的token保存在Redis
               redisTemplate.opsForValue().set(token,userId+"",cacheExpire);
               //把新的token绑定到线程
               threadLocalToken.setToken(token);
            } else {
                //如果redis不存在令牌(token),让用户重新登录
                resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
                resp.getWriter().print("令牌过期，请重新登录");
                return false;
            }
        } catch(JWTDecodeException e){
            resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
            resp.getWriter().print("Invalide Token");
            return false;
        }

            boolean bool = executeLogin(request,response);
            return bool;
    }
    @Override
    protected
    boolean onLoginFailure(AuthenticationToken token,
                           AuthenticationException e,
                           ServletRequest request,
                           ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
        resp.setContentType("application/json;charset=utf-8");
        resp.setHeader("Access-Control-Allow-Credentials","true");
        resp.setHeader("Access-Control-Allow-Origin",req.getHeader("Origin"));
        try{
            resp.getWriter().print(e.getMessage());
        } catch (IOException exception){

        }
        return false;
    }

    private String getRequestToken(HttpServletRequest httpServletRequest){
        //从Header中获取token
        String token = httpServletRequest.getHeader("token");
        //如果header中不存在token,则从参数中获取toekn
        if(StringUtils.isBlank(token)){
            token = httpServletRequest.getParameter("token");
        }
        return token;
    }

    @Override
    public
    void doFilterInternal(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain) throws ServletException, IOException {
        super.doFilterInternal(request, response, chain);
    }
}
